﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace ProceduralCity.Models.Buildings
{
    class RoundedBuilding : BuildingBase
    {
        private readonly int numSegments;

        private TexturedQuad[] walls;
        private VertexPositionNormalTexture[] roof;
        private int[] roofIndices;

        /// <summary>
        /// Generates an elliptical building that fits within the specified bounding box.
        /// </summary>
        /// <param name="device"></param>
        /// <param name="boxBounds"></param>
        public RoundedBuilding(BoundingBox boxBounds, int numSegments)
        {
            this.numSegments = numSegments;
            this.walls = new TexturedQuad[numSegments];

            Vector3 centerTop = (boxBounds.Max + boxBounds.Min) / 2;
            centerTop.Y = boxBounds.Max.Y;
            Vector3 size = boxBounds.Max - boxBounds.Min;

            // generate round building
            Vector3[] roof = MakeEllipse(centerTop, size.X / 3, size.Z / 2);
            Vector3[] normals = GetNormals(centerTop, roof);
            for (int i = 0; i < numSegments; ++i)
            {
                float segmentWidth = Vector3.Distance(roof[i], roof[(i + 1) % numSegments]);
                walls[i] = new TexturedQuad(roof[i], normals[i], Vector3.Up, segmentWidth, size.Y);
                Vector2 textCoordUpperLeft = new Vector2(i / (float)numSegments, 0);
                Vector2 textCoordLowerRight = new Vector2((i + 1) / (float)numSegments, 1);
                walls[i].SetTextureCoordinates(textCoordUpperLeft, textCoordLowerRight);
            }
            CreateRoof(centerTop, roof);
        }

        private void CreateRoof(Vector3 center, Vector3[] roofCoords)
        {
            roof = new VertexPositionNormalTexture[roofCoords.Length + 1];
            roofIndices = new int[roofCoords.Length * 3];

            roof[0] = new VertexPositionNormalTexture(center, Vector3.Up, Vector2.Zero);
            for (int i = 0; i < roofCoords.Length; ++i)
            {
                roof[i + 1] = new VertexPositionNormalTexture(roofCoords[i], Vector3.Up, Vector2.Zero);

                int indexStart = 3 * i;
                roofIndices[indexStart + 0] = i + 1;
                roofIndices[indexStart + 1] = 0;
                roofIndices[indexStart + 2] = i + 2;
            }
            roofIndices[roofIndices.Length - 1] = 1;
        }

        private Vector3[] MakeEllipse(Vector3 center, float xRadius, float zRadius)
        {
            Vector3[] vertices = new Vector3[numSegments];

            float segmentAngle = MathHelper.TwoPi / numSegments;
            float angle = 0;
            for (int i = 0; i < numSegments; ++i, angle += segmentAngle)
            {
                // Remember that in the y=0 plane, X acts like the y-axis, and Z acts like the x-axis.
                float z = zRadius * (float)Math.Cos(angle);
                float x = xRadius * (float)Math.Sin(angle);

                Vector3 position = new Vector3(x, 0, z);
                vertices[i] = center + position;
            }

            return vertices;
        }

        private Vector3[] GetNormals(Vector3 center, Vector3[] ellipsePoints)
        {
            Vector3[] normals = new Vector3[ellipsePoints.Length];

            for (int i = 0; i < ellipsePoints.Length; ++i)
            {
                Vector3 a = ellipsePoints[i];
                Vector3 b = ellipsePoints[(i + 1) % ellipsePoints.Length];

                Vector3 slope = b - a;
                Vector3 perpendicular = new Vector3(-slope.Z, slope.Y, slope.X);

                perpendicular.Normalize();
                normals[i] = perpendicular;
            }

            return normals;
        }

        protected override void DrawInternal(GraphicsDevice device, BasicEffect buildingEffect)
        {
            buildingEffect.Texture = wallTexture;
            foreach (EffectPass pass in buildingEffect.CurrentTechnique.Passes)
            {
                pass.Apply();
                foreach (TexturedQuad quad in walls)
                {
                    device.DrawUserIndexedPrimitives(PrimitiveType.TriangleList,
                        quad.Vertices, 0, quad.Vertices.Length,
                        quad.Indices, 0, quad.Indices.Length / 3);
                }
            }

            buildingEffect.Texture = roofTexture;
            foreach (EffectPass pass in buildingEffect.CurrentTechnique.Passes)
            {
                pass.Apply();
                device.DrawUserIndexedPrimitives(PrimitiveType.TriangleList,
                    roof, 0, roof.Length,
                    roofIndices, 0, roofIndices.Length / 3);
            }
        }
    }
}
